/* =======================================================================
 * Sistemi Operativi, Corso di Laurea in Ingegneria Informatica, DM 270/04
 * A.A. 2011/2012, Facoltà di Ingegneria, Università della Calabria
 * =======================================================================
 */

package sisop.appelli.a2012.treaule;

import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author Marco Lackovic <mlackovic@deis.unical.it>
 * @version 1.0, Jul 27, 2012
 */
public class AuleLC extends Aule {

	/*
	 * Uso un solo lucchetto perché le aule non sono tra di loro indipendenti,
	 * né possono essere considerate a parte dal resto: l'uscita dalle aule
	 * condiziona sia la scelta dell'aula che la chiamata dello studente
	 * successivo.
	 */
	private Lock lock = new ReentrantLock();

	/*
	 * Questa condition serve per sospendere gli studenti nel momento in cui
	 * cercano di accedere alle aule, in attesa di essere chiamati dal docente.
	 */
	private Condition possoEntrare = lock.newCondition();

	/*
	 * Questa condition serve per sospendere il docente nello stato iniziale,
	 * quando gli studenti non sono ancora arrivati e in quello finale quando
	 * non ce ne sono più, quando deve aspettare che lo studente che ha fatto
	 * entrare nell'aula completi la sua operazione, prima di chiamare lo
	 * studente successivo e quando sono terminati i posti in tutt'e tre le
	 * aule.
	 */
	private Condition prossimaChiamata = lock.newCondition();

	/*
	 * Questa lista serve per garantire il risveglio degli studenti in ordine
	 * FIFO, e per sapere se ci sono studenti da chiamare.
	 */
	private LinkedList<Thread> fila = new LinkedList<Thread>();

	/*
	 * Questa variabile booleana serve per sapere se lo studente chiamato dal
	 * docente è entrato e quindi è possibile chiamare lo studente successivo.
	 */
	private boolean studenteEntrato = true;

	@Override
	public int accedi() throws InterruptedException {
		int miaAula;
		lock.lock();
		try {
			prossimaChiamata.signal();
			fila.add(Thread.currentThread());
			while (!turnoStudente()) {
				possoEntrare.await();
			}
			fila.removeFirst();
			miaAula = aulaProssimoStudente;
			studenteEntrato = true;
		} finally {
			lock.unlock();
		}
		return miaAula;
	}

	private boolean turnoStudente() {
		return !studenteEntrato && fila.getFirst() == Thread.currentThread();
	}

	@Override
	public void esci(int aula) throws InterruptedException {
		lock.lock();
		try {
			postiDisponibili[aula]++;
			totalePostiDisponibili++;
			prossimaChiamata.signal();
		} finally {
			lock.unlock();
		}
	}

	@Override
	public void chiamaStudente() throws InterruptedException {
		lock.lock();
		try {
			while (!turnoDocente()) {
				prossimaChiamata.await();
			}
			aggiornaAule();
			totalePostiDisponibili--;
			studenteEntrato = false;
			possoEntrare.signalAll();
		} finally {
			lock.unlock();
		}
	}

	private boolean turnoDocente() {
		return studenteEntrato && totalePostiDisponibili > 0 && fila.size() > 0;
	}

}
